<template>
  <div class="editable-excel-container">
    <!-- 工具栏 -->
    <div class="toolbar">
      <el-button type="primary" @click="handleSave" :loading="saving">保存</el-button>
      <el-button @click="handleReset">重置</el-button>
      <el-button @click="handleExport">导出Excel</el-button>
    </div>

    <!-- 表格容器 -->
    <div class="table-wrapper" ref="tableWrapper">
      <el-table
        v-if="tableData.length > 0"
        :data="tableData"
        border
        style="width: 100%"
        :max-height="height"
        class="editable-table"
        :span-method="handleSpanMethod"
      >
        <el-table-column
          v-for="(column, index) in tableColumns"
          :key="index"
          :prop="column.prop"
          :label="column.label"
          :width="column.width"
          align="left"
        >
          <template #default="scope">
            <el-input
              v-if="editable && !isCellMerged(scope.$index, index)"
              v-model="scope.row[column.prop]"
              @blur="handleCellChange(scope.row, column.prop, scope.row[column.prop])"
              size="small"
              class="cell-input"
            />
            <span v-else>{{ scope.row[column.prop] || '' }}</span>
          </template>
        </el-table-column>
        <el-table-column v-if="editable" label="操作" width="80" align="center" fixed="right">
          <template #default="scope">
            <el-button type="danger" size="small" link @click="handleDeleteRow(scope.$index)">
              删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
      <div v-else class="empty-state">
        <el-empty description="暂无数据"></el-empty>
      </div>
    </div>

    <!-- 添加行按钮 -->
    <div v-if="editable" class="add-row-container">
      <el-button type="primary" @click="handleAddRow" size="small">添加行</el-button>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, watch, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import * as XLSX from 'xlsx'

const props = defineProps({
  url: {
    type: String,
    default: ''
  },
  file: {
    type: [Blob, ArrayBuffer],
    default: null
  },
  editable: {
    type: Boolean,
    default: true
  },
  height: {
    type: [String, Number],
    default: 500
  }
})

const emit = defineEmits(['save', 'change'])

const tableData = ref([])
const tableColumns = ref([])
const originalData = ref([])
const saving = ref(false)
const tableWrapper = ref(null)
const mergedCells = ref([])
const mergeMap = ref(new Map())

const readExcelFromBuffer = async (buffer) => {
  let arrayBuffer
  if (buffer instanceof Blob) {
    arrayBuffer = await buffer.arrayBuffer()
  } else if (buffer instanceof ArrayBuffer) {
    arrayBuffer = buffer
  } else {
    throw new Error('不支持的文件类型')
  }
  return arrayBuffer
}

const loadExcelData = async () => {
  let arrayBuffer = null

  try {
    if (props.file) {
      arrayBuffer = await readExcelFromBuffer(props.file)
    } else if (props.url) {
      const response = await fetch(props.url)
      if (!response.ok) {
        throw new Error('加载 Excel 文件失败')
      }
      arrayBuffer = await response.arrayBuffer()
    } else {
      tableData.value = []
      tableColumns.value = []
      return
    }

    const workbook = XLSX.read(arrayBuffer, {
      type: 'array',
      cellDates: true,
      cellNF: false,
      cellText: false
    })

    const firstSheetName = workbook.SheetNames[0]
    const worksheet = workbook.Sheets[firstSheetName]

    if (worksheet['!merges']) {
      mergedCells.value = worksheet['!merges'].map((merge) => ({
        s: { r: merge.s.r, c: merge.s.c },
        e: { r: merge.e.r, c: merge.e.c }
      }))
      buildMergeMap()
    } else {
      mergedCells.value = []
      mergeMap.value.clear()
    }

    const jsonData = XLSX.utils.sheet_to_json(worksheet, {
      header: 1,
      defval: '',
      raw: false
    })

    if (jsonData.length === 0) {
      tableData.value = []
      tableColumns.value = []
      return
    }

    const headers = jsonData[0] || []
    const rows = jsonData.slice(1)

    const columns = headers.map((header, index) => ({
      prop: `col${index}`,
      label: String(header || `列${index + 1}`),
      width: 150
    }))

    const data = rows.map((row, rowIndex) => {
      const rowData = {}
      headers.forEach((header, colIndex) => {
        const value = row[colIndex]
        const mergeInfo = getMergeInfo(rowIndex, colIndex)
        if (mergeInfo && mergeInfo.isMerged && !mergeInfo.isMain) {
          const mainRow = rows[mergeInfo.mainRow]
          rowData[`col${colIndex}`] =
            mainRow && mainRow[colIndex] !== undefined ? String(mainRow[colIndex] || '') : ''
        } else {
          rowData[`col${colIndex}`] = value !== undefined && value !== null ? String(value) : ''
        }
      })
      return rowData
    })

    tableColumns.value = columns
    tableData.value = data
    originalData.value = JSON.parse(JSON.stringify(data))
  } catch (error) {
    ElMessage.error('加载 Excel 文件失败: ' + (error.message || '未知错误'))
    tableData.value = []
    tableColumns.value = []
  }
}

const buildMergeMap = () => {
  mergeMap.value.clear()
  mergedCells.value.forEach((merge) => {
    if (merge.s.r > 0) {
      const rowStart = merge.s.r - 1
      const rowEnd = merge.e.r - 1
      const colStart = merge.s.c
      const colEnd = merge.e.c

      for (let r = rowStart; r <= rowEnd; r++) {
        for (let c = colStart; c <= colEnd; c++) {
          const key = `${r}-${c}`
          mergeMap.value.set(key, {
            isMerged: true,
            isMain: r === rowStart && c === colStart,
            mainRow: rowStart,
            mainCol: colStart,
            rowspan: rowEnd - rowStart + 1,
            colspan: colEnd - colStart + 1
          })
        }
      }
    }
  })
}

const getMergeInfo = (rowIndex, colIndex) => {
  const key = `${rowIndex}-${colIndex}`
  return mergeMap.value.get(key)
}

const isCellMerged = (rowIndex, colIndex) => {
  const mergeInfo = getMergeInfo(rowIndex, colIndex)
  return mergeInfo && mergeInfo.isMerged && !mergeInfo.isMain
}

const handleSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  const colIndex = columnIndex
  const mergeInfo = getMergeInfo(rowIndex, colIndex)

  if (mergeInfo && mergeInfo.isMerged) {
    if (mergeInfo.isMain) {
      return {
        rowspan: mergeInfo.rowspan,
        colspan: mergeInfo.colspan
      }
    } else {
      return {
        rowspan: 0,
        colspan: 0
      }
    }
  }

  return {
    rowspan: 1,
    colspan: 1
  }
}

const handleCellChange = (row, prop, value) => {
  const rowIndex = tableData.value.indexOf(row)
  const colIndex = parseInt(prop.replace('col', ''))

  const mergeInfo = getMergeInfo(rowIndex, colIndex)
  if (mergeInfo && mergeInfo.isMerged && mergeInfo.isMain) {
    for (let r = mergeInfo.mainRow; r < mergeInfo.mainRow + mergeInfo.rowspan; r++) {
      for (let c = mergeInfo.mainCol; c < mergeInfo.mainCol + mergeInfo.colspan; c++) {
        if (r !== rowIndex || c !== colIndex) {
          const mergeKey = `${r}-${c}`
          const mergeCellInfo = mergeMap.value.get(mergeKey)
          if (mergeCellInfo && !mergeCellInfo.isMain) {
            if (tableData.value[r]) {
              tableData.value[r][`col${c}`] = value
            }
          }
        }
      }
    }
  }

  emit('change', { row, prop, value, tableData: tableData.value })
}

const handleAddRow = () => {
  const newRow = {}
  tableColumns.value.forEach((column) => {
    newRow[column.prop] = ''
  })
  tableData.value.push(newRow)
  emit('change', { action: 'add', row: newRow, tableData: tableData.value })
}

const handleDeleteRow = (index) => {
  tableData.value.splice(index, 1)
  emit('change', { action: 'delete', index, tableData: tableData.value })
}

const convertToExcelData = () => {
  const headers = tableColumns.value.map((col) => col.label)
  const rows = tableData.value.map((row) => {
    return tableColumns.value.map((col) => row[col.prop] || '')
  })
  return [headers, ...rows]
}

const convertToExcelFile = () => {
  try {
    const wb = XLSX.utils.book_new()
    const excelData = convertToExcelData()
    const ws = XLSX.utils.aoa_to_sheet(excelData)

    if (mergedCells.value.length > 0) {
      const merges = mergedCells.value.map((merge) => {
        return {
          s: { r: merge.s.r, c: merge.s.c },
          e: { r: merge.e.r, c: merge.e.c }
        }
      })
      ws['!merges'] = merges
    }

    XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')

    const wbout = XLSX.write(wb, {
      bookType: 'xlsx',
      type: 'array',
      bookSST: false
    })

    return new Blob([wbout], {
      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    })
  } catch (error) {
    throw error
  }
}

const handleSave = async () => {
  if (!props.editable) {
    return
  }

  saving.value = true
  try {
    const excelBlob = convertToExcelFile()
    const excelData = convertToExcelData()

    emit('save', {
      file: excelBlob,
      data: tableData.value,
      columns: tableColumns.value,
      excelData: excelData,
      fileName: `excel_${new Date().getTime()}.xlsx`
    })

    originalData.value = JSON.parse(JSON.stringify(tableData.value))
  } catch (error) {
    // 忽略错误，由外层处理
  } finally {
    saving.value = false
  }
}

const handleReset = () => {
  tableData.value = JSON.parse(JSON.stringify(originalData.value))
}

const handleExport = () => {
  try {
    const wb = XLSX.utils.book_new()
    const excelData = convertToExcelData()
    const ws = XLSX.utils.aoa_to_sheet(excelData)

    if (mergedCells.value.length > 0) {
      const merges = mergedCells.value.map((merge) => {
        return {
          s: { r: merge.s.r, c: merge.s.c },
          e: { r: merge.e.r, c: merge.e.c }
        }
      })
      ws['!merges'] = merges
    }

    XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')

    const fileName = `excel_${new Date().getTime()}.xlsx`
    XLSX.writeFile(wb, fileName)
  } catch (error) {
    ElMessage.error('导出失败')
  }
}

watch(
  () => [props.url, props.file],
  () => {
    if (props.url || props.file) {
      loadExcelData()
    }
  },
  { immediate: true }
)

defineExpose({
  getData: () => tableData.value,
  getColumns: () => tableColumns.value,
  getExcelData: () => convertToExcelData(),
  save: handleSave,
  reset: handleReset,
  export: handleExport
})
</script>

<style scoped lang="scss">
.editable-excel-container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;

  .toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 20px;
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
  }

  .table-wrapper {
    flex: 1;
    overflow: auto;
    border: 1px solid #e8e8e8;
    border-radius: 4px;

    .editable-table {
      width: 100%;

      :deep(.cell-input) {
        .el-input__wrapper {
          box-shadow: none;
          border: none;
          padding: 0;
        }

        .el-input__inner {
          padding: 0 5px;
          height: 32px;
          line-height: 32px;
        }
      }

      :deep(.el-table__cell) {
        padding: 0;
      }

      :deep(.el-table__body-wrapper) {
        .el-table__row {
          .el-table__cell {
            padding: 0;
          }
        }
      }
    }

    .empty-state {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 400px;
    }
  }

  .add-row-container {
    margin-top: 10px;
    padding: 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
  }
}
</style>
